package be.nepherte.movmanager.client.ui;

import java.util.List;

import be.nepherte.movmanager.client.MovManager;
import be.nepherte.movmanager.client.ui.components.FilterPanel;
import be.nepherte.movmanager.client.ui.components.AbstractMoviesView;
import be.nepherte.movmanager.client.ui.tabframework.Tab;
import be.nepherte.movmanager.client.util.MoviesViewFactory;
import be.nepherte.movmanager.client.util.MoviesViewFactory.MoviesViewType;
import be.nepherte.movmanager.domain.Movie;
import be.nepherte.movmanager.domain.UserInfo;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * This class represents the My Movies tab of MovManger. This page allows
 * the user to view his personal movie collection.

 * <p>
 * Copyright © 2010 Bart Verhoeven
 * </p>
 * 
 * <p>
 * This file is part of MovManager.
 * </p>
 * 
 * <p>
 * MovManager is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * </p>
 * 
 * <p>
 * MovManager is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * </p>
 * 
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * MovManager. If not, see http://www.gnu.org/licenses/.
 * </p>
 * 
 * @author 	Bart Verhoeven
 * @version	0.1
 */
public class MyMoviesTab extends Tab {
	/**Main panel holding all other panels.*/
	private final VerticalPanel pnlMain = new VerticalPanel();
	/**Top panel holding all panels located at the top.*/
	private final HorizontalPanel pnlTop = new HorizontalPanel();
	/**Panel holding a particular MoviesView.*/
	private final VerticalPanel pnlMovies = new VerticalPanel();
	/**List box for selecting how many movie titles per page to show.*/
	private final ListBox lstMoviesPerPage = new ListBox();
	/**List box containing all the possible views.*/
	private final ListBox lstMoviesViews = new ListBox();
	/**List with all the movies.*/
	private List<Movie> allMovies;
	/**Panel holding the filter settings.*/
	private FilterPanel pnlFilter = new FilterPanel();
	/**currently active movies view.*/
	private AbstractMoviesView moviesView;
	/**User info of the current user.*/
	private final UserInfo userInfo;
	/**Wrapper panel holding the navigation elements and filter panel.*/
	private final HorizontalPanel pnlFiltersAndNavigation = new HorizontalPanel();
	/**Filtered movies.*/
	private List<Movie> filteredMovies;

	/**
	 * Creates, initializes and returns MyMoviesTab.
	 */
	public static TabInfo init(final UserInfo userInfo) {
		return new TabInfo(MovManager.constants.myMoviesTabName(), MovManager.constants.myMoviesTabDescription(), MovManager.constants.myMoviesTabColor()) {

			@Override
			public Tab createInstance() {
				return new MyMoviesTab(userInfo);
			}
		};
	}

	/**
	 * Creates a new MyMoviesTab. To create an object of this class, use
	 * {@link #init()}.
	 */
	private MyMoviesTab(UserInfo userInfo) {
		super();
		this.userInfo = userInfo;
		initMainPanel();
		updateMovies();
	}
	
	/**
	 * Initializes the top panel.
	 */
	private void initializeTopPanel() {
		formatTopPanel();
		initalizeTopLeftPanel();
		initializeFilterNavigationPanel();
		initializeTopRightPanel();		
	}

	/**
	 * Formats the top panel.
	 */
	private void formatTopPanel() {
		pnlTop.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
		pnlTop.setWidth("100%");
	}
	
	/**
	 * Initializes a counter part for the top right panel to keep the top panel
	 * aligned properly.
	 */
	private void initalizeTopLeftPanel() {
		HorizontalPanel pnlTopLeft = new HorizontalPanel();
		pnlTopLeft.setSpacing(5);
		pnlTop.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
		pnlTopLeft.add(new Image(GWT.getModuleBaseURL() + "images/blank.png"));
		pnlTopLeft.add(new Image(GWT.getModuleBaseURL() + "images/blank.png"));
		pnlTopLeft.add(new Image(GWT.getModuleBaseURL() + "images/blank.png"));
		pnlTop.add(pnlTopLeft);
	}	

	/**
	 * Initializes the filter panel and the navigational elements.
	 */
	private void initializeFilterNavigationPanel() {
		pnlFiltersAndNavigation.add(pnlFilter);
		pnlFilter.setFilterAction(new ClickHandler() {	
			@Override
			public void onClick(ClickEvent event) {
				filteredMovies = pnlFilter.filter(allMovies);
				moviesView.setMovies(filteredMovies);
			}
		});
		
		// Add filter and navigation panel to top panel
		pnlTop.add(pnlFiltersAndNavigation);
		pnlTop.setCellHorizontalAlignment(pnlFiltersAndNavigation, HasHorizontalAlignment.ALIGN_CENTER);
	}
	
	/**
	 * Initializes the top right panel.
	 */
	private void initializeTopRightPanel() {
		// Create drop down list with amount of movies on 1 page
		for (int i = 5; i <= 25; i+=5)
			lstMoviesPerPage.addItem(Integer.toString(i));
		lstMoviesPerPage.setVisibleItemCount(1);
		lstMoviesPerPage.setSelectedIndex(1);
		lstMoviesPerPage.addChangeHandler(new MoviesPerPageChangeHandler());
		
		for (MoviesViewType t: MoviesViewType.values())
			lstMoviesViews.addItem(t.toString());
		lstMoviesViews.addChangeHandler(new MoviesViewChangeHandler());
		
		// Set tool tips
		lstMoviesPerPage.setTitle("Number of movies on 1 page");
		lstMoviesViews.setTitle("Choose movie view");
		
		// Add items to top right panel
		pnlTop.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
		HorizontalPanel pnlTopRight = new HorizontalPanel();
		pnlTopRight.setSpacing(10);
		pnlTopRight.add(lstMoviesPerPage);
		pnlTopRight.add(lstMoviesViews);
		pnlTop.add(pnlTopRight);
	}

	/**
	 * Initializes the main panel.
	 */
	private void initMainPanel() {
		initializeTopPanel();
		formatMainPanel();
		initializeMoviesView(MoviesViewFactory.getDefaultMoviesView());
		pnlMain.add(pnlTop);
		pnlMain.add(pnlMovies);
		initWidget(pnlMain);
	}

	/**
	 *Formats the main panel.
	 */
	private void formatMainPanel() {
		pnlMain.setWidth("100%");
		pnlMain.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
	}

	/**
	 * Shows the given movies in the currently active movies view.
	 * 
	 * @param	movies
	 * 			the movies to be displayed
	 */
	private void showMovies(List<Movie> movies) {
		// There are no new movies: do nothing
		if (movies.equals(allMovies))
			return;

		allMovies = movies;
		pnlFilter.reset();
		pnlMovies.clear();
		
		// no movies in the collection
		if (movies.isEmpty()) {
			Label noMovies = new Label("There are currently no movies in your personal collection.");
			noMovies.addStyleName("boldStyle");
			pnlMovies.add(noMovies);
		}
		// the movie collection changed
		else {
			moviesView.setMovies(movies, 0);
			pnlMovies.add(moviesView);
		}
	}
	
	/**
	 * Initializes the movies view.
	 * 
	 * @param	moviesView
	 * 			the movies view to initialize
	 */
	private void initializeMoviesView(AbstractMoviesView moviesView) {
		// note: GWT SDK API specifically states widgets only implement HasClickHandlers so cast is safe
		Widget leftNavigationElement = (Widget) moviesView.getLeftNavigationElement();
		Widget rightNavigationElement = (Widget) moviesView.getRightNavigationElement();
		
		if (this.moviesView != null) {
			// recalculate movie index
			int firstMovieIndex = moviesView.calculateFirstMovieIndex(this.moviesView.getFirstMovieIndex());
			moviesView.setFirstMovieIndex(firstMovieIndex);
			
			// remove navigation arrows of the old view
			pnlFiltersAndNavigation.remove(0);
			pnlFiltersAndNavigation.remove(pnlFiltersAndNavigation.getWidgetCount()-1);
		}
		
		int moviesPerPage = getMoviesPerPage();
		moviesView.setMaxVisibleElements(moviesPerPage);
		
		// set the navigation arrows of the new view
		pnlFiltersAndNavigation.insert(leftNavigationElement, 0);
		pnlFiltersAndNavigation.add(rightNavigationElement);
		pnlFiltersAndNavigation.setCellVerticalAlignment(leftNavigationElement, HasVerticalAlignment.ALIGN_MIDDLE);
		pnlFiltersAndNavigation.setCellVerticalAlignment(rightNavigationElement, HasVerticalAlignment.ALIGN_MIDDLE);
		
		// replace movies view with new one
		pnlMovies.clear();
		pnlMovies.add(moviesView);
		this.moviesView = moviesView;
	}

	/**
	 * Specifies what happens upon showing this page: display user's movies.
	 * When the movie filter is still active, show the filtered movie results
	 * instead. Whenever the movie collection has changed, the filter will be
	 * reset and the first movies of the new collection will be displayed.
	 */
	@Override
	public void onShow() {
		updateMovies();
	}

	/**
	 * Requests the user's movies and displays them in the current movies view.
	 */
	private void updateMovies() {
		showMovies(userInfo.getMovies());
	}

	@Override
	public void onHide() {
	}
	
	/**
	 * Gets the currently selected movies view.
	 */
	private AbstractMoviesView getSelectedMoviesView() {
		int selectedMoviesViewIndex = lstMoviesViews.getSelectedIndex();
		MoviesViewType moviesViewType = MoviesViewType.valueOf(lstMoviesViews.getValue(selectedMoviesViewIndex));
		AbstractMoviesView moviesView = MoviesViewFactory.createMoviesView(moviesViewType);
		return moviesView;
	}
	
	/**
	 * Gets the current number of maximum movies on one page.
	 */
	private int getMoviesPerPage() {
		int selectedIndex = lstMoviesPerPage.getSelectedIndex();
		String selectedValue = lstMoviesPerPage.getValue(selectedIndex);
		int moviesPerPage = Integer.parseInt(selectedValue);
		return moviesPerPage;
	}
	
	/**
	 * Change handler for list box with movies view.
	 */
	private class MoviesViewChangeHandler implements ChangeHandler {
		
		@Override
		public void onChange(ChangeEvent event) {
			AbstractMoviesView moviesView = getSelectedMoviesView();
			initializeMoviesView(moviesView);
			List<Movie> movies = pnlFilter.isActive() ? filteredMovies : allMovies;
			int firstMovieIndex = moviesView.getFirstMovieIndex();
			moviesView.setMovies(movies, firstMovieIndex);
			
		}
	}
	
	/**
	 * Change handler for list box with max number of movies on 1 page.
	 */
	private class MoviesPerPageChangeHandler implements ChangeHandler {

		@Override
		public void onChange(ChangeEvent event) {
			int moviesPerPage = getMoviesPerPage();
			moviesView.setMaxVisibleElements(moviesPerPage);
		}
	}
}